package message

import (
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/arjunxw/iothub-south-client/config"
	errors2 "gitee.com/arjunxw/iothub-south-client/errors"
	req "gitee.com/arjunxw/iothub-south-client/request"
	"gitee.com/arjunxw/iothub-south-client/service"
	"gitee.com/arjunxw/iothub-south-client/util"
	"github.com/google/wire"
	"go.uber.org/zap"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
)

type IotHubMessage interface {
	http.Handler
	RegisterMessageHandler(handler IotHubMessageHandler)
	UnregisterMessageHandler(notifyType string)
}

type IotHubMessageHandler interface {
	NotifyType() string
	Handle(downstream *Downstream) error
}

var (
	errMsgBodyReadFailed               = errors.New("message body read failed")
	errMsgBodyParsingFailed            = errors.New("message body parsing failed")
	errMsgNotificationTypeIsBlank      = errors.New("message notification type is blank")
	errMsgActionDataIsEmpty            = errors.New("the action data of the message body is empty")
	errMsgActionDataUnableParse        = errors.New("unable to parse the action data of the message body")
	errMsgAddDeviceDataIsEmpty         = errors.New("the add device data of message body is empty")
	errMsgAddDeviceUnableParse         = errors.New("unable to parse the added device data of the message body")
	errMsgUpgradeDataIsEmpty           = errors.New("the device upgrade data of the message body is empty")
	errMsgUpgradeDataUnableParse       = errors.New("unable to parse the device upgrade data of the message body")
	errMsgSerialNetDataIsEmpty         = errors.New("the serial net data of the message body is empty")
	errMsgSerialNetDataUnableParse     = errors.New("unable to parse the serial net data of the message body")
	errMsgSimulatorSendDataIsEmpty     = errors.New("the simulator send data of the message body is empty")
	errMsgSimulatorSendDataUnableParse = errors.New("unable to parse the simulator send data of the message body")
)

var ProviderSet = wire.NewSet(NewIotHubMessage)

type iotHubMessage struct {
	messageHandlerMap map[string]IotHubMessageHandler
	dalInfoService    service.DalInfoService
	request           req.Request
	log               *zap.Logger
	cfg               config.Config
}

func NewIotHubMessage(cfg config.Config, log *zap.Logger, request req.Request, dalInfoService service.DalInfoService) IotHubMessage {
	iothubMessage := &iotHubMessage{
		cfg:               cfg,
		request:           request,
		dalInfoService:    dalInfoService,
		log:               log,
		messageHandlerMap: make(map[string]IotHubMessageHandler)}
	iothubMessage.RegisterMessageHandler(NewCheckStatusHandler(dalInfoService))
	return iothubMessage
}

func (ihm *iotHubMessage) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	response.Header().Add("Content-Type", "application/json;charset=utf-8")

	defer func() {
		if rec := recover(); rec != nil {
			ihm.log.Error("", zap.Any("err", rec))
			response.WriteHeader(http.StatusInternalServerError)
			writeJson(response, req.BuildResponseFailed().SetBody(rec))
		}
	}()

	downstream, err := ihm.unmarshal(request)
	if err != nil {
		ihm.handleError(response, err)
		return
	}

	err = ihm.dispatch(downstream)
	if err != nil {
		ihm.handleError(response, err)
		return
	}

	response.WriteHeader(http.StatusOK)
	writeJson(response, req.BuildResponseOk())
}

func (ihm *iotHubMessage) RegisterMessageHandler(handler IotHubMessageHandler) {
	if handler.NotifyType() == NotifyUpgrade {
		handler = newUpgradeInHandler(handler, ihm.request, ihm.cfg.GetHost())
	}
	ihm.messageHandlerMap[handler.NotifyType()] = handler
}

func (ihm *iotHubMessage) UnregisterMessageHandler(notifyType string) {
	delete(ihm.messageHandlerMap, notifyType)
}

func (ihm *iotHubMessage) handleError(response http.ResponseWriter, err error) {
	response.WriteHeader(http.StatusOK)
	if errors.Is(err, errors2.ErrIothubResponsePending) {
		body := req.BuildResponseOk().SetBody(map[string]interface{}{"result": "Pendding"})
		writeJson(response, body)
		return
	}
	ihm.log.Error("", zap.Error(err))
	if errors.Is(err, errMsgBodyReadFailed) ||
		errors.Is(err, errMsgBodyParsingFailed) ||
		errors.Is(err, errMsgNotificationTypeIsBlank) ||
		errors.Is(err, errMsgActionDataIsEmpty) ||
		errors.Is(err, errMsgActionDataUnableParse) ||
		errors.Is(err, errMsgAddDeviceDataIsEmpty) ||
		errors.Is(err, errMsgAddDeviceUnableParse) ||
		errors.Is(err, errMsgUpgradeDataIsEmpty) ||
		errors.Is(err, errMsgUpgradeDataUnableParse) ||
		errors.Is(err, errMsgSerialNetDataIsEmpty) ||
		errors.Is(err, errMsgSerialNetDataUnableParse) ||
		errors.Is(err, errMsgSimulatorSendDataIsEmpty) ||
		errors.Is(err, errMsgSimulatorSendDataUnableParse) {
		writeJson(response, req.BuildResponseIllegalArgument().SetMessage(err.Error()))
		return
	}

	writeJson(response, req.BuildResponseFailed().SetMessage(err.Error()))
}

func writeJson(response http.ResponseWriter, value interface{}) {
	if buf, err := json.Marshal(value); err == nil {
		response.Write(buf)
	}
}

func (ihm *iotHubMessage) dispatch(downstream *Downstream) error {
	if downstream == nil {
		return errors.New("downstream data is nil")
	}

	if handler, ok := ihm.messageHandlerMap[downstream.NotifyType]; ok && handler != nil {
		return handler.Handle(downstream)
	}
	return nil
}

func (ihm *iotHubMessage) unmarshal(request *http.Request) (*Downstream, error) {
	buf, err := ioutil.ReadAll(request.Body)
	if err != nil {
		return nil, errMsgBodyReadFailed
	}

	downstream := Downstream{}
	err = json.Unmarshal(buf, &downstream)
	if err != nil {
		return nil, errMsgBodyParsingFailed
	}

	if strings.TrimSpace(downstream.NotifyType) == "" {
		return nil, errMsgNotificationTypeIsBlank
	}

	switch downstream.NotifyType {
	case NotifyCheckStatus:
		err = ihm.toCheckStatus(&downstream)
	case NotifyAction:
		err = ihm.toAction(&downstream)
	case NotifyAddDevice:
		err = ihm.toAddDevice(&downstream)
	case NotifyDeleteDevice:
		err = ihm.toDeleteDevice(&downstream)
	case NotifyProductAdded:
		err = ihm.toProductAdded(&downstream)
	case NotifyProductDelete:
		err = ihm.toProductDelete(&downstream)
	case NotifyUpgrade:
		err = ihm.toUpgrade(&downstream)
	case NotifySerialNet:
		err = ihm.toSerialNet(&downstream)
	case NotifySimulatorSend:
		err = ihm.toSimulatorSend(&downstream)
	default:
		// err = iothub_south_sdk.NewIotHubResponse()
	}

	if err != nil {
		return nil, err
	}

	return &downstream, nil
}

func (ihm *iotHubMessage) toCheckStatus(ds *Downstream) error {
	return nil
}

func (ihm *iotHubMessage) toAction(ds *Downstream) error {
	if ds.Data == nil {
		return errMsgActionDataIsEmpty
	}

	if data, ok := ds.Data.(map[string]interface{}); ok {
		actionData := &DsDeviceActionData{}
		if actionCode, ok := data["actionCode"]; ok {
			actionData.ActionCode = util.JsonValueToString(actionCode)
		}
		if params, ok := data["params"]; ok {
			actionData.Params = params.(map[string]interface{})
		}
		ds.Data = actionData
	} else {
		return errMsgActionDataUnableParse
	}

	return nil
}

func (ihm *iotHubMessage) toAddDevice(ds *Downstream) error {
	if ds.Data == nil {
		return errMsgAddDeviceDataIsEmpty
	}

	if data, ok := ds.Data.(map[string]interface{}); ok {
		addData := &DsDeviceAddData{}
		if name, ok := data["name"]; ok {
			addData.Name = util.JsonValueToString(name)
		}
		if remark, ok := data["remark"]; ok {
			addData.Remark = util.JsonValueToString(remark)
		}
		ds.Data = addData
	} else {
		return errMsgAddDeviceUnableParse
	}

	return nil
}

func (ihm *iotHubMessage) toDeleteDevice(ds *Downstream) error {
	return nil
}

// Product addition data may be empty
func (ihm *iotHubMessage) toProductAdded(ds *Downstream) error {
	//if ds.Data == nil {
	//	return iothub_south_sdk.NewIotHubResponse(dto.IllegalArgument, "product add data is empty", nil)
	//}
	//
	//if _, ok := ds.Data.(map[string]interface{}); !ok {
	//	return iothub_south_sdk.NewIotHubResponse(dto.IllegalArgument, "unable to parse product add data", nil)
	//}
	//
	return nil
}

func (ihm *iotHubMessage) toProductDelete(ds *Downstream) error {
	return nil
}

func (ihm *iotHubMessage) toUpgrade(ds *Downstream) error {
	if ds.Data == nil {
		return errMsgUpgradeDataIsEmpty
	}

	if data, ok := ds.Data.(map[string]interface{}); ok {
		upgradeData := &DsUpgradeInData{}
		if upgradeId, ok := data["upgradeId"]; ok {
			upgradeData.UpgradeID = util.JsonValueToString(upgradeId)
		}
		if snList, ok := data["snList"]; ok {
			upgradeData.SnList = snList.([]interface{})
		}
		if fs, ok := data["firmsoft"]; ok {
			fs := fs.(map[string]interface{})
			fInfo := &FirmsoftInfo{}
			if fid, ok := fs["firmsoftId"]; ok {
				fInfo.FirmsoftID = util.JsonValueToString(fid)
			}
			if name, ok := fs["name"]; ok {
				fInfo.Name = util.JsonValueToString(name)
			}
			if version, ok := fs["version"]; ok {
				fInfo.Version = util.JsonValueToString(version)
			}
			if sign, ok := fs["sign"]; ok {
				fInfo.Sign = util.JsonValueToString(sign)
			}
			if signAlgo, ok := fs["signAlgo"]; ok {
				fInfo.SignAlgo = util.JsonValueToString(signAlgo)
			}
			upgradeData.Firmsoft = fInfo
		}
		ds.Data = upgradeData
	} else {
		return errMsgUpgradeDataUnableParse
	}

	return nil
}

func (ihm *iotHubMessage) toSerialNet(ds *Downstream) error {
	if ds.Data == nil {
		return errMsgSerialNetDataIsEmpty
	}

	if data, ok := ds.Data.(map[string]interface{}); ok {
		serialData := &DsSerialNetData{}
		serialData.Data = util.JsonValueToString(data["data"])
		dataLen := data["dataLen"]
		if l, ok := dataLen.(float64); ok {
			serialData.DataLen = int32(l)
		} else {
			l, err := strconv.ParseInt(fmt.Sprintf("%v", dataLen), 10, 64)
			if err != nil {
				return err
			}
			serialData.DataLen = int32(l)
		}
		ds.Data = serialData
	} else {
		return errMsgSerialNetDataUnableParse
	}

	return nil
}

func (ihm *iotHubMessage) toSimulatorSend(ds *Downstream) error {
	if ds.Data == nil {
		return errMsgSimulatorSendDataIsEmpty
	}

	if data, ok := ds.Data.(map[string]interface{}); ok {
		simulatorSendData := &DsSimulatorSendData{}
		simulatorSendData.Content = util.JsonValueToString(data["content"])
		simulatorSendData.Binary = data["binary"].(bool)
		ds.Data = simulatorSendData
	} else {
		return errMsgSimulatorSendDataUnableParse
	}

	return nil
}
